Bug 447998 - GtkBuilder does not support building parts of the xml tree
authorPaolo Borelli <pborelli@katamail.com>
Wed, 16 Jul 2008 15:36:53 +0000 (15:36 +0000)
committerPaolo Borelli <pborelli@src.gnome.org>
Wed, 16 Jul 2008 15:36:53 +0000 (15:36 +0000)
2008-07-15  Paolo Borelli  <pborelli@katamail.com>

Bug 447998 - GtkBuilder does not support building parts of the xml tree

* gtk/gtkbuilder.c:
* gtk/gtkbuilder.h:
* gtk/gtkbuilderprivate.h:
* gtk/gtkbuilderparser.c:
* gtk/gtk.symbols:
Add two new functions that allow cherry picking and construct
objects from a ui description file or string.

* gtk/tests/builder.c: tests for the above.

svn path=/trunk/; revision=20845

ChangeLog
docs/reference/gtk/gtk-sections.txt
gtk/gtk.symbols
gtk/gtkbuilder.c
gtk/gtkbuilder.h
gtk/gtkbuilderparser.c
gtk/gtkbuilderprivate.h
gtk/tests/builder.c

index 7257e58088f401a0316057c117c2b5523ac92151..3e20563fa2c2eeb548215b59494970bc22ca0c8d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2008-07-15  Paolo Borelli  <pborelli@katamail.com>
+
+       Bug 447998 - GtkBuilder does not support building parts of the xml tree
+
+       * gtk/gtkbuilder.c:
+       * gtk/gtkbuilder.h:
+       * gtk/gtkbuilderprivate.h:
+       * gtk/gtkbuilderparser.c:
+       * gtk/gtk.symbols:
+       Add two new functions that allow cherry picking and construct
+       objects from a ui description file or string.
+
+       * gtk/tests/builder.c: tests for the above.
+
 2008-07-15  Paolo Borelli  <pborelli@katamail.com>
 
        * gtk/tests/builder.c: fix up broken test (cellview has no "clicked"
index 6c5d9a1a4b7e7bbed0c6dd6467ea04abb2b03eb4..2d22a82b33354739e4dfd0d2b983eaeef3a30efc 100644 (file)
@@ -478,6 +478,8 @@ GtkBuilderError
 gtk_builder_new
 gtk_builder_add_from_file
 gtk_builder_add_from_string
+gtk_builder_add_objects_from_file
+gtk_builder_add_objects_from_string
 gtk_builder_get_object
 gtk_builder_get_objects
 gtk_builder_connect_signals
index ad6f331b210d67a7246d58305372996c531de83f..fcef780595f77e777ed97e7696fcc2181e36ead8 100644 (file)
@@ -445,6 +445,8 @@ gtk_buildable_set_buildable_property
 #if IN_FILE(__GTK_BUILDER_C__)
 gtk_builder_add_from_file
 gtk_builder_add_from_string
+gtk_builder_add_objects_from_file
+gtk_builder_add_objects_from_string
 gtk_builder_error_quark
 gtk_builder_get_object
 gtk_builder_get_objects
index 8ba1da951b1cd75e52f54c43a15e5ad03ec2fcd5..1ea7db3ac7508b1a9ed6042c1ab9a6422dfc96d9 100644 (file)
@@ -658,7 +658,7 @@ gtk_builder_add_from_file (GtkBuilder   *builder,
 
   g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
   g_return_val_if_fail (filename != NULL, 0);
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, 0);
 
   tmp_error = NULL;
 
@@ -673,6 +673,70 @@ gtk_builder_add_from_file (GtkBuilder   *builder,
 
   _gtk_builder_parser_parse_buffer (builder, filename,
                                     buffer, length,
+                                    NULL,
+                                    &tmp_error);
+
+  g_free (buffer);
+
+  if (tmp_error != NULL)
+    {
+      g_propagate_error (error, tmp_error);
+      return 0;
+    }
+
+  return 1;
+}
+
+/**
+ * gtk_builder_add_objects_from_file:
+ * @builder: a #GtkBuilder
+ * @filename: the name of the file to parse
+ * @object_ids: nul-terminated array of objects to build
+ * @error: return location for an error, or %NULL
+ *
+ * Parses a file containing a <link linkend="BUILDER-UI">GtkBuilder 
+ * UI definition</link> building only the requested objects and merges
+ * them with the current contents of @builder. 
+ *
+ * <note><para>
+ * If you are adding an object that depends on an object that is not 
+ * its child (for instance a #GtkTreeView that depends on its
+ * #GtkTreeModel), you have to explicitely list all of them in @object_ids. 
+ * </para></note>
+ *
+ * Returns: A positive value on success, 0 if an error occurred
+ *
+ * Since: 2.14
+ **/
+guint
+gtk_builder_add_objects_from_file (GtkBuilder   *builder,
+                                   const gchar  *filename,
+                                   gchar       **object_ids,
+                                   GError      **error)
+{
+  gchar *buffer;
+  gsize length;
+  GError *tmp_error;
+
+  g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
+  g_return_val_if_fail (filename != NULL, 0);
+  g_return_val_if_fail (object_ids != NULL && object_ids[0] != NULL, 0);
+  g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+  tmp_error = NULL;
+
+  if (!g_file_get_contents (filename, &buffer, &length, &tmp_error))
+    {
+      g_propagate_error (error, tmp_error);
+      return 0;
+    }
+  
+  g_free (builder->priv->filename);
+  builder->priv->filename = g_strdup (filename);
+
+  _gtk_builder_parser_parse_buffer (builder, filename,
+                                    buffer, length,
+                                    object_ids,
                                     &tmp_error);
 
   g_free (buffer);
@@ -695,7 +759,7 @@ gtk_builder_add_from_file (GtkBuilder   *builder,
  *
  * Parses a string containing a <link linkend="BUILDER-UI">GtkBuilder 
  * UI definition</link> and merges it with the current contents of @builder. 
- * 
+ *
  * Returns: A positive value on success, 0 if an error occurred
  *
  * Since: 2.12
@@ -710,7 +774,7 @@ gtk_builder_add_from_string (GtkBuilder   *builder,
 
   g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
   g_return_val_if_fail (buffer != NULL, 0);
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, 0);
 
   tmp_error = NULL;
 
@@ -719,6 +783,7 @@ gtk_builder_add_from_string (GtkBuilder   *builder,
 
   _gtk_builder_parser_parse_buffer (builder, "<input>",
                                     buffer, length,
+                                    NULL,
                                     &tmp_error);
   if (tmp_error != NULL)
     {
@@ -729,6 +794,61 @@ gtk_builder_add_from_string (GtkBuilder   *builder,
   return 1;
 }
 
+/**
+ * gtk_builder_add_objects_from_string:
+ * @builder: a #GtkBuilder
+ * @buffer: the string to parse
+ * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
+ * @object_ids: nul-terminated array of objects to build
+ * @error: return location for an error, or %NULL
+ *
+ * Parses a string containing a <link linkend="BUILDER-UI">GtkBuilder 
+ * UI definition</link> building only the requested objects and merges
+ * them with the current contents of @builder. 
+ * 
+ * <note><para>
+ * If you are adding an object that depends on an object that is not 
+ * its child (for instance a #GtkTreeView that depends on its
+ * #GtkTreeModel), you have to explicitely list all of them in @object_ids. 
+ * </para></note>
+ *
+ * Returns: A positive value on success, 0 if an error occurred
+ *
+ * Since: 2.14
+ **/
+guint
+gtk_builder_add_objects_from_string (GtkBuilder   *builder,
+                                     const gchar  *buffer,
+                                     gsize         length,
+                                     gchar       **object_ids,
+                                     GError      **error)
+{
+  GError *tmp_error;
+
+  g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
+  g_return_val_if_fail (buffer != NULL, 0);
+  g_return_val_if_fail (object_ids != NULL && object_ids[0] != NULL, 0);
+  g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+  tmp_error = NULL;
+
+  g_free (builder->priv->filename);
+  builder->priv->filename = g_strdup (".");
+
+  _gtk_builder_parser_parse_buffer (builder, "<input>",
+                                    buffer, length,
+                                    object_ids,
+                                    &tmp_error);
+
+  if (tmp_error != NULL)
+    {
+      g_propagate_error (error, tmp_error);
+      return 0;
+    }
+
+  return 1;
+}
+
 /**
  * gtk_builder_get_object:
  * @builder: a #GtkBuilder
index e6fe549c00ce2f1305f5f867727aabdbc315f033..d33e32c306fe408e80c6658047d808226ec309ee 100644 (file)
@@ -100,6 +100,15 @@ guint        gtk_builder_add_from_string         (GtkBuilder    *builder,
                                                   const gchar   *buffer,
                                                   gsize          length,
                                                   GError       **error);
+guint        gtk_builder_add_objects_from_file   (GtkBuilder    *builder,
+                                                  const gchar   *filename,
+                                                  gchar        **object_ids,
+                                                  GError       **error);
+guint        gtk_builder_add_objects_from_string (GtkBuilder    *builder,
+                                                  const gchar   *buffer,
+                                                  gsize          length,
+                                                  gchar        **object_ids,
+                                                  GError       **error);
 GObject*     gtk_builder_get_object              (GtkBuilder    *builder,
                                                   const gchar   *name);
 GSList*      gtk_builder_get_objects             (GtkBuilder    *builder);
index 9e073a426d6fbd48048ddcef05e8d4f386ff09aa..d1057811a3435eae926bb89adf43f44640fa4859 100644 (file)
@@ -282,6 +282,21 @@ parse_requires (ParserData   *data,
   req_info->tag.name = element_name;
 }
 
+static gboolean
+is_requested_object (const gchar *object,
+                     ParserData  *data)
+{
+  GSList *l;
+
+  for (l = data->requested_objects; l; l = l->next)
+    {
+      if (strcmp (l->data, object) == 0)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
 static void
 parse_object (ParserData   *data,
               const gchar  *element_name,
@@ -346,6 +361,25 @@ parse_object (ParserData   *data,
       return;
     }
 
+  ++data->cur_object_level;
+
+  /* check if we reached a requested object (if it is specified) */
+  if (data->requested_objects && !data->inside_requested_object)
+    {
+      if (is_requested_object (object_id, data))
+        {
+          data->requested_object_level = data->cur_object_level;
+
+          GTK_NOTE (BUILDER, g_print ("requested object \"%s\" found at level %d\n",
+                                      object_id,
+                                      data->requested_object_level));
+
+          data->inside_requested_object = TRUE;
+        }
+      else
+        return;
+    }
+
   object_info = g_slice_new0 (ObjectInfo);
   object_info->class_name = object_class;
   object_info->id = object_id;
@@ -801,6 +835,11 @@ start_element (GMarkupParseContext *context,
     parse_requires (data, element_name, names, values, error);
   else if (strcmp (element_name, "object") == 0)
     parse_object (data, element_name, names, values, error);
+  else if (data->requested_objects && !data->inside_requested_object)
+    {
+      /* If outside a requested object, simply ignore this tag */
+      return;
+    }
   else if (strcmp (element_name, "child") == 0)
     parse_child (data, element_name, names, values, error);
   else if (strcmp (element_name, "property") == 0)
@@ -910,11 +949,32 @@ end_element (GMarkupParseContext *context,
                         GTK_MAJOR_VERSION, GTK_MINOR_VERSION);
        }
     }
+  else if (strcmp (element_name, "interface") == 0)
+    {
+    }
+  else if (data->requested_objects && !data->inside_requested_object)
+    {
+      /* If outside a requested object, simply ignore this tag */
+      return;
+    }
   else if (strcmp (element_name, "object") == 0)
     {
       ObjectInfo *object_info = state_pop_info (data, ObjectInfo);
       ChildInfo* child_info = state_peek_info (data, ChildInfo);
 
+      if (data->requested_objects && data->inside_requested_object &&
+          (data->cur_object_level == data->requested_object_level))
+        {
+          GTK_NOTE (BUILDER, g_print ("requested object end found at level %d\n",
+                                      data->requested_object_level));
+
+          data->inside_requested_object = FALSE;
+        }
+
+      --data->cur_object_level;
+
+      g_assert (data->cur_object_level >= 0);
+
       object_info->object = builder_construct (data, object_info, error);
       if (!object_info->object)
        {
@@ -976,9 +1036,6 @@ end_element (GMarkupParseContext *context,
       object_info->signals =
         g_slist_prepend (object_info->signals, signal_info);
     }
-  else if (strcmp (element_name, "interface") == 0)
-    {
-    }
   else if (strcmp (element_name, "placeholder") == 0)
     {
     }
@@ -1056,6 +1113,7 @@ _gtk_builder_parser_parse_buffer (GtkBuilder   *builder,
                                   const gchar  *filename,
                                   const gchar  *buffer,
                                   gsize         length,
+                                  gchar       **requested_objs,
                                   GError      **error)
 {
   ParserData *data;
@@ -1066,13 +1124,31 @@ _gtk_builder_parser_parse_buffer (GtkBuilder   *builder,
   data->filename = filename;
   data->domain = g_strdup (gtk_builder_get_translation_domain (builder));
 
+  data->requested_objects = NULL;
+  if (requested_objs)
+    {
+      gint i;
+
+      data->inside_requested_object = FALSE;
+      for (i = 0; requested_objs[i]; ++i)
+        {
+          data->requested_objects = g_slist_prepend (data->requested_objects,
+                                                     g_strdup (requested_objs[i]));    
+        }
+    }
+  else
+    {
+      /* get all the objects */
+      data->inside_requested_object = TRUE;
+    }
+
   data->ctx = g_markup_parse_context_new (&parser, 
                                           G_MARKUP_TREAT_CDATA_AS_TEXT, 
                                           data, NULL);
 
   if (!g_markup_parse_context_parse (data->ctx, buffer, length, error))
     goto out;
-  
+
   _gtk_builder_finish (builder);
 
   /* Custom parser_finished */
@@ -1103,6 +1179,8 @@ _gtk_builder_parser_parse_buffer (GtkBuilder   *builder,
   g_slist_foreach (data->custom_finalizers, (GFunc)free_subparser, NULL);
   g_slist_free (data->custom_finalizers);
   g_slist_free (data->finalizers);
+  g_slist_foreach (data->requested_objects, (GFunc) g_free, NULL);
+  g_slist_free (data->requested_objects);
   g_free (data->domain);
   g_markup_parse_context_free (data->ctx);
   g_free (data);
index b860e6e12c5838de1cf2efbe5da54ceafa779f9d..8c69630d6c91f93e29544ac9432f276a0a8d2d8e 100644 (file)
@@ -96,6 +96,11 @@ typedef struct {
   const gchar *filename;
   GSList *finalizers;
   GSList *custom_finalizers;
+
+  GSList *requested_objects; /* NULL if all the objects are requested */
+  gboolean inside_requested_object;
+  gint requested_object_level;
+  gint cur_object_level;
 } ParserData;
 
 typedef GType (*GTypeGetFunc) (void);
@@ -105,6 +110,7 @@ void _gtk_builder_parser_parse_buffer (GtkBuilder *builder,
                                        const gchar *filename,
                                        const gchar *buffer,
                                        gsize length,
+                                       gchar **requested_objs,
                                        GError **error);
 GObject * _gtk_builder_construct (GtkBuilder *builder,
                                   ObjectInfo *info,
index 115abc77531255624197f56e9f0e26415c4b4782..9d1e4a82b82738b41b08cbe0513e793f3081a0a9 100644 (file)
@@ -2105,7 +2105,6 @@ test_pango_attributes (void)
   g_error_free (error);
 }
 
-
 static void
 test_requires (void)
 {
@@ -2127,6 +2126,133 @@ test_requires (void)
   g_error_free (error);
 }
 
+static void
+test_add_objects (void)
+{
+  GtkBuilder *builder;
+  GError *error;
+  gint ret;
+  GObject *obj;
+  GtkUIManager *manager;
+  GtkWidget *menubar;
+  GObject *menu, *label;
+  GList *children;
+  gchar *objects[2] = {"mainbox", NULL};
+  gchar *objects2[3] = {"mainbox", "window2", NULL};
+  gchar *objects3[2] = {"uimgr1", NULL};
+  const gchar buffer[] =
+    "<interface>"
+    "  <object class=\"GtkWindow\" id=\"window\">"
+    "    <child>"
+    "      <object class=\"GtkVBox\" id=\"mainbox\">"
+    "        <property name=\"visible\">True</property>"
+    "        <child>"
+    "          <object class=\"GtkLabel\" id=\"label1\">"
+    "            <property name=\"visible\">True</property>"
+    "            <property name=\"label\" translatable=\"no\">first label</property>"
+    "          </object>"
+    "        </child>"
+    "        <child>"
+    "          <object class=\"GtkLabel\" id=\"label2\">"
+    "            <property name=\"visible\">True</property>"
+    "            <property name=\"label\" translatable=\"no\">second label</property>"
+    "          </object>"
+    "          <packing>"
+    "            <property name=\"position\">1</property>"
+    "          </packing>"
+    "        </child>"
+    "      </object>"
+    "    </child>"
+    "  </object>"
+    "  <object class=\"GtkWindow\" id=\"window2\">"
+    "    <child>"
+    "      <object class=\"GtkLabel\" id=\"label1\">"
+    "        <property name=\"label\" translatable=\"no\">second label</property>"
+    "      </object>"
+    "    </child>"
+    "  </object>"
+    "<interface/>";
+  const gchar buffer2[] =
+    "<interface>"
+    "  <object class=\"GtkUIManager\" id=\"uimgr1\">"
+    "    <child>"
+    "      <object class=\"GtkActionGroup\" id=\"ag1\">"
+    "        <child>"
+    "          <object class=\"GtkAction\" id=\"file\">"
+    "            <property name=\"label\">_File</property>"
+    "          </object>"
+    "          <accelerator key=\"n\" modifiers=\"GDK_CONTROL_MASK\"/>"
+    "        </child>"
+    "      </object>"
+    "    </child>"
+    "    <ui>"
+    "      <menubar name=\"menubar1\">"
+    "        <menu action=\"file\">"
+    "        </menu>"
+    "      </menubar>"
+    "    </ui>"
+    "  </object>"
+    "  <object class=\"GtkWindow\" id=\"window1\">"
+    "    <child>"
+    "      <object class=\"GtkMenuBar\" id=\"menubar1\" constructor=\"uimgr1\"/>"
+    "    </child>"
+    "  </object>"
+    "</interface>";
+
+  error = NULL;
+  builder = gtk_builder_new ();
+  ret = gtk_builder_add_objects_from_string (builder, buffer, -1, objects, &error);
+  g_assert (ret);
+  g_assert (error == NULL);
+  obj = gtk_builder_get_object (builder, "window");
+  g_assert (obj == NULL);
+  obj = gtk_builder_get_object (builder, "window2");
+  g_assert (obj == NULL);
+  obj = gtk_builder_get_object (builder, "mainbox");  
+  g_assert (GTK_IS_WIDGET (obj));
+  g_object_unref (builder);
+
+  error = NULL;
+  builder = gtk_builder_new ();
+  ret = gtk_builder_add_objects_from_string (builder, buffer, -1, objects2, &error);
+  g_assert (ret);
+  g_assert (error == NULL);
+  obj = gtk_builder_get_object (builder, "window");
+  g_assert (obj == NULL);
+  obj = gtk_builder_get_object (builder, "window2");
+  g_assert (GTK_IS_WINDOW (obj));
+  gtk_widget_destroy (GTK_WIDGET (obj));
+  obj = gtk_builder_get_object (builder, "mainbox");  
+  g_assert (GTK_IS_WIDGET (obj));
+  g_object_unref (builder);
+
+  /* test cherry picking a ui manager */
+  error = NULL;
+  builder = gtk_builder_new ();
+  ret = gtk_builder_add_objects_from_string (builder, buffer2, -1, objects3, &error);
+  g_assert (ret);
+  obj = gtk_builder_get_object (builder, "uimgr1");
+  g_assert (GTK_IS_UI_MANAGER (obj));
+  manager = GTK_UI_MANAGER (obj);
+  obj = gtk_builder_get_object (builder, "file");
+  g_assert (GTK_IS_ACTION (obj));
+  menubar = gtk_ui_manager_get_widget (manager, "/menubar1");
+  g_assert (GTK_IS_MENU_BAR (menubar));
+
+  children = gtk_container_get_children (GTK_CONTAINER (menubar));
+  menu = children->data;
+  g_assert (menu != NULL);
+  g_assert (GTK_IS_MENU_ITEM (menu));
+  g_assert (strcmp (GTK_WIDGET (menu)->name, "file") == 0);
+  g_list_free (children);
+  label = G_OBJECT (GTK_BIN (menu)->child);
+  g_assert (label != NULL);
+  g_assert (GTK_IS_LABEL (label));
+  g_assert (strcmp (gtk_label_get_text (GTK_LABEL (label)), "File") == 0);
+
+  g_object_unref (builder);
+}
 
 static void 
 test_file (const gchar *filename)
@@ -2212,6 +2338,7 @@ main (int argc, char **argv)
   g_test_add_func ("/Builder/IconFactory", test_icon_factory);
   g_test_add_func ("/Builder/PangoAttributes", test_pango_attributes);
   g_test_add_func ("/Builder/Requires", test_requires);
+  g_test_add_func ("/Builder/AddObjects", test_add_objects);
 
   return g_test_run();
 }